package com.ndp.fb.mq.service.publishhandleservice.pre;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ndp.fb.constant.CommonConstants;
import com.ndp.fb.enums.publish.PublishResultType;
import com.ndp.fb.enums.publish.PublishStatus;
import com.ndp.fb.exception.ErrorCode;
import com.ndp.fb.exception.PrePublishProcessException;
import com.ndp.fb.mq.api.service.publishhandleservice.PublishPreProcessor;
import com.ndp.fb.mq.api.service.publishhandleservice.PublishPreProcessorHandle;
import com.ndp.fb.mq.service.schedulerservice.adpublish.PublishCacheMongoServiceFailover;
import com.ndp.fb.rdb.api.PublishBatchRdbService;
import com.ndp.fb.rdb.api.PublishResultDetailRdbService;
import com.ndp.fb.rdb.api.PublishResultRdbService;
import com.ndp.fb.rdb.api.PublishResultRecordRdbService;
import com.ndp.fb.rdb.model.PublishBatchResult;
import com.ndp.fb.rdb.model.PublishResultDetail;
import com.ndp.fb.rdb.model.PublishResultRecord;
import com.ndp.fb.redis.api.RedisClient;
import com.ndp.fb.redis.model.AdPublishCache;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by hunk.qin on 2016/1/14.
 */
@Component
public class PublishPreProcessorHandleImpl implements PublishPreProcessorHandle,BeanPostProcessor {

    private PublishPreProcessorHandleExe preProcessorHandleExecute;

    private Map<Class,PublishPreProcessor> proccessMap=new HashMap<>();

    /**
     * 需要执行的前置实现的顺序，在一个实现内可以并发执行，但大的前置任务之间为顺序执行
     * 这里只是声明了执行的顺序，会不会别执行取决于 proccessList 里面是否有该实例
     */
    private static final Class [] sortPreProcessorClass ={
            PublishPreVideoUploadImp.class,
            PublishPreMaterielCreateImpl.class
    };


    @Override
    public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        if(ArrayUtils.contains(targetClass.getInterfaces(),PublishPreProcessor.class)){
            registerHandle((PublishPreProcessor) bean);
        }else if(bean instanceof PublishPreProcessorHandleExe){
            preProcessorHandleExecute=(PublishPreProcessorHandleExe)bean;
        }
        return bean;
    }


    @Override
    public PublishPreProcessor[] sortPreProcessor() {
        PublishPreProcessor[] sortPreProcessor=new PublishPreProcessor[proccessMap.size()];
        for (int i = 0; i <sortPreProcessorClass.length ; i++) {
            sortPreProcessor[i]=proccessMap.get(sortPreProcessorClass[i]);
        }
        return  sortPreProcessor;
    }

    @Override
    public void registerHandle(PublishPreProcessor publishPreProcessor) {
        proccessMap.put(publishPreProcessor.getClass(),publishPreProcessor);
    }

    @Override
    public void handle(String batchid) {
        preProcessorHandleExecute.execute(batchid,sortPreProcessor());
    }

}

/**
 * Created by hunk.qin on 2016/1/19.
 */
@Component
 class PublishPreProcessorHandleExe {

    Logger logger = LoggerFactory.getLogger(getClass());
    @Reference
    private PublishBatchRdbService publishBathRdbService;
    @Reference
    private PublishResultRdbService publishResultRdbService;
    @Reference
    private PublishResultRecordRdbService publishResultRecordRdbService;

    @Reference
    private PublishResultDetailRdbService publishResultDetailRdbService;

    @Autowired
    private PublishCacheMongoServiceFailover publishCacheMongoService;

    @Autowired
    private RedisClient redisClient;



    public void execute(String batchid,PublishPreProcessor[] sortPreProcessor){
        String batchIdKey = "batchId_" + batchid;
        publishBathRdbService.updatePublishStatusById(Long.valueOf(batchid), PublishStatus.PRE_PROCESSING.getCode());
        logger.info("get ads publish cache by batchIdkey:{}",batchIdKey);
        AdPublishCache cache = JSONObject.parseObject( publishCacheMongoService.getCache(batchIdKey),AdPublishCache.class);
        try {
            if(cache==null){
            	logger.info("get ads publish cache by batchIdkey:{} is null,try get redis",batchIdKey);
                // failover ,if mongo not has cache ,try get for redis
                cache  = redisClient.getAdPublishCache(batchIdKey);
                if(cache==null){
                    logger.info("redis get ads publish cache by batchIdkey:{} is null too,exception",batchIdKey);
                    publishBathRdbService.updatePublishStatusById(Long.valueOf(batchid), PublishStatus.FAIL.getCode());
                    throw  new PrePublishProcessException(ErrorCode.ERROR_20003,batchIdKey);
                }else{
                    logger.warn("batchId :"+batchIdKey+"save cache to mongo failover ,cache not exist ,try get cache for reids ,is true, save again to mongo");
                    publishCacheMongoService.save(JSONObject.toJSONString(cache, SerializerFeature.DisableCircularReferenceDetect));
                    logger.warn("batchId :"+batchIdKey+"try save done");
                }

            }
            Set<Class> preSucceed=cache.getPreSucceed();
            for (PublishPreProcessor publishPreProcessor : sortPreProcessor) {
                    if(publishPreProcessor!=null&&!preSucceed.contains(publishPreProcessor.getClass())){
                        if(publishPreProcessor!=null&&publishPreProcessor.check(cache)){
                            logger.info("start执行前置条件:{}，batchIdKey:{}",publishPreProcessor.getClass().getName(),batchIdKey);
                            String result=publishPreProcessor.refreshCache(batchIdKey,Long.valueOf(batchid),cache);
                            if(!result.equals(CommonConstants.TRUE)){
                                throw new PrePublishProcessException(result);
                            }
                            logger.info("end执行前置条件:{}，batchIdKey:{}",publishPreProcessor.getClass().getName(),batchIdKey);

                        }else{
                            logger.info("前置 handle null or 检查为 false，不执行，handle:{},batchIdKey:{}",publishPreProcessor.getClass().getName(),batchIdKey);
                        }

                        preSucceed.add(publishPreProcessor.getClass());
                    }
            }
            //全部正常执行完成后，修改batch 到待发布状态
            publishBathRdbService.updatePublishStatusById(Long.valueOf(batchid),PublishStatus.WAIT_PUPLISH.getCode());
            logger.info("所有前置执行完成,batchIdKey:{}",batchIdKey);
        }catch (Exception e){
            // TODO: 2016/3/11 update pre job done to mongo ,to hunk
            saveErrorLogAndUpdateBatchStatus(batchid,e.getMessage());
            logger.error("batchIdKey:{},pre handle error,msg:",batchIdKey,e.getMessage(),e);

        }
    }

    private void saveErrorLogAndUpdateBatchStatus(String batchId,String errormsg){
        //一个批次多个记录，获取第一个 记录设置
        publishBathRdbService.updatePublishFail(Long.valueOf(batchId),CommonConstants.PRE_FAIL);

        List<PublishBatchResult> publishBatchResults = publishResultRdbService.searchPublishResultInfo(Long.valueOf(batchId));
        Long publishResultId = publishBatchResults.get(0).getPublishResultId();

        PublishResultRecord publishResultRecord = new PublishResultRecord();
        publishResultRecord.setPublishResultId(publishResultId);
        publishResultRecord.setType(PublishResultType.preProcessPublishFail.getIndex());
        Long publishResultRecordId = publishResultRecordRdbService.save(publishResultRecord);

        PublishResultDetail publishResultDetail = new PublishResultDetail();
        publishResultDetail.setPublishResultRecordId(publishResultRecordId);
        publishResultDetail.setResult(PublishResultType.preProcessPublishFail.name());
        publishResultDetail.setObjectType("prePublishProcess");
        publishResultDetail.setParam("");
        if(StringUtils.isNotEmpty(errormsg)){
            publishResultDetail.setException(errormsg.length()>4000?errormsg.substring(0,3900):errormsg);
        }else{
            publishResultDetail.setException("null exception");
        }
        publishResultDetailRdbService.save(publishResultDetail);

    }
}
